
//
// Copyright (c) 2008, the /*jupiter jazz*/ group
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the /*jupiter jazz*/ group nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY the /*jupiter jazz*/ group ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL the /*jupiter jazz*/ group BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

// Interface header.
#include "progressiveexrimagefilewriter.h"

// classlib headers.
#include "classlib/canvasproperties.h"
#include "classlib/tile.h"

// OpenEXR headers.
#include "openexr/ImfChannelList.h"
#include "openexr/ImfFrameBuffer.h"
#include "openexr/ImfHeader.h"
#include "openexr/ImfPixelType.h"
#include "openexr/ImfTileDescription.h"

// Standard headers.
#include <cassert>
#include <memory>

using namespace Iex;
using namespace Imath;
using namespace Imf;
using namespace std;

namespace foundation
{

//
// ProgressiveEXRImageFileWriter class implementation.
//

namespace
{

    const char* ChannelName[] = { "R", "G", "B", "A" };

}   // anonymous namespace

struct ProgressiveEXRImageFileWriter::Impl
{
    int                             m_thread_count;
    auto_ptr<Imf::TiledOutputFile>  m_file;
    CanvasProperties                m_props;
    PixelType                       m_pixel_type;
};

// Constructor.
ProgressiveEXRImageFileWriter::ProgressiveEXRImageFileWriter(const size_t thread_count)
  : impl(new Impl())
{
    impl->m_thread_count = static_cast<int>(thread_count);
}

// Destructor.
ProgressiveEXRImageFileWriter::~ProgressiveEXRImageFileWriter()
{
    if (is_open())
        close();

    delete impl;
}

// Open an image file for writing.
void ProgressiveEXRImageFileWriter::open(
    const char*             filename,
    const CanvasProperties& props)
{
    assert(filename);
    assert(!is_open());

    try
    {
        // todo: lift this limitation.
        assert(props.m_channel_count <= 4);

        // Figure out the pixel type, based on the pixel format of the image.
        impl->m_pixel_type = FLOAT;
        switch (props.m_pixel_format)
        {
          case PixelFormatUInt32: impl->m_pixel_type = UINT; break;
          case PixelFormatHalf: impl->m_pixel_type = HALF; break;
          case PixelFormatFloat: impl->m_pixel_type = FLOAT; break;
          default: throw ExceptionUnsupportedImageFormat();
        }

        // Construct TileDescription object.
        const TileDescription tile_desc(
            static_cast<unsigned int>(props.m_tile_width),
            static_cast<unsigned int>(props.m_tile_height),
            ONE_LEVEL);

        // Construct ChannelList object.
        ChannelList channels;
        for (size_t c = 0; c < props.m_channel_count; ++c)
            channels.insert(ChannelName[c], Channel(impl->m_pixel_type));

        // Construct Header object.
        Header header(
            static_cast<int>(props.m_canvas_width),
            static_cast<int>(props.m_canvas_height),
            static_cast<float>(props.m_canvas_width) / props.m_canvas_height);
        header.setTileDescription(tile_desc);
        header.channels() = channels;

        // Create the output file.
        impl->m_file.reset(
            new TiledOutputFile(
                filename,
                header,
                impl->m_thread_count));

        // Store the canvas properties.
        impl->m_props = props;
    }
    catch (const BaseExc& e)
    {
        // I/O error.
        throw ExceptionIOError(e.what());
    }
}

// Close the image file.
void ProgressiveEXRImageFileWriter::close()
{
    assert(is_open());
    impl->m_file.reset();
}

// Return true if an image file is currently open.
bool ProgressiveEXRImageFileWriter::is_open() const
{
    return impl->m_file.get() != 0;
}

// Write a tile to the image file.
void ProgressiveEXRImageFileWriter::write_tile(
    const Tile&             tile,
    const size_t            tile_x,
    const size_t            tile_y)
{
    assert(is_open());

    try
    {
        const int ix              = static_cast<int>(tile_x);
        const int iy              = static_cast<int>(tile_y);
        const Box2i range         = impl->m_file->dataWindowForTile(ix, iy);
        const size_t channel_size = pixel_size(tile.get_pixel_format());
        const size_t stride_x     = channel_size * impl->m_props.m_channel_count;
        const size_t stride_y     = stride_x * tile.get_width();
        const size_t tile_origin  = range.min.x * stride_x + range.min.y * stride_y;
        const char* tile_base     = reinterpret_cast<const char*>(tile.pixel(0, 0)) - tile_origin;

        // Construct FrameBuffer object.
        FrameBuffer framebuffer;
        for (size_t c = 0; c < impl->m_props.m_channel_count; ++c)
        {
            const char* base = tile_base + c * channel_size;
            framebuffer.insert(
                ChannelName[c],
                Slice(
                    impl->m_pixel_type,
                    const_cast<char*>(base),
                    stride_x,
                    stride_y));
        }

        // Write tile.
        impl->m_file->setFrameBuffer(framebuffer);
        impl->m_file->writeTile(ix, iy);
    }
    catch (const BaseExc& e)
    {
        // I/O error.
        throw ExceptionIOError(e.what());
    }
}

}   // namespace foundation
